package jmine.tec.utils.spring;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;

/**
 * @author wellington.santos
 * @created May 29, 2012
 */
public class MockSpringBeanFactory implements ListableBeanFactory {

    private static final String[] EMPTY_STRING_ARRAY = new String[0];

    private Map<String, Object> singletonMap = new HashMap<String, Object>();

    private Map<String, Class<?>> prototypeMap = new HashMap<String, Class<?>>();

    private Map<String, Class<?>> everybodyMap = new HashMap<String, Class<?>>();

    private Map<Class<?>, Set<String>> namesForTypes = new HashMap<Class<?>, Set<String>>();

    private void register(String name, Class<?> type) {
        if (type != null) {
            Set<String> names = this.namesForTypes.get(type);
            if (names == null) {
                names = new TreeSet<String>();
                this.namesForTypes.put(type, names);
            }
            names.add(name);
            this.register(name, type.getSuperclass());
            for (Class<?> interf : type.getInterfaces()) {
                this.register(name, interf);
            }
        }
    }

    public <T> T setSingleton(T bean) {
        if (bean == null) {
            throw new IllegalArgumentException("bean instance is null");
        }
        this.setSingleton(bean.getClass().getName(), bean);
        return bean;
    }

    public <T> T setSingleton(String name, T bean) {
        if (bean == null) {
            throw new IllegalArgumentException("bean instance is null");
        }
        if (name == null) {
            throw new IllegalArgumentException("bean name is null");
        }
        this.everybodyMap.put(name, bean.getClass());
        this.singletonMap.put(name, bean);
        if (this.prototypeMap.remove(name) != null) {
            for (Set<String> nameSet : this.namesForTypes.values()) {
                nameSet.remove(name);
            }
        }
        this.register(name, bean.getClass());
        return bean;
    }

    public void setPrototype(Class<?> type) {
        if (type == null) {
            throw new IllegalArgumentException("bean class is null");
        }
        this.setPrototype(type.getName(), type);
    }

    public void setPrototype(String name, Class<?> type) {
        if (type == null) {
            throw new IllegalArgumentException("bean class is null");
        }
        if (name == null) {
            throw new IllegalArgumentException("bean name is null");
        }
        this.everybodyMap.put(name, type);
        this.prototypeMap.put(name, type);
        if (this.singletonMap.remove(name) != null) {
            for (Set<String> nameSet : this.namesForTypes.values()) {
                nameSet.remove(name);
            }
        }
        this.register(name, type);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public Object getBean(String name) throws BeansException {
        return this.getBean(name, (Object[]) null);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public Object getBean(String name, Class requiredType) throws BeansException {
        Object bean = this.getBean(name, (Object[]) null);
        if (requiredType != null && !requiredType.isAssignableFrom(bean.getClass())) {
            throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
        }
        return bean;
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public Object getBean(String name, Object[] args) throws BeansException {
        if (args != null && args.length > 0) {
            throw new UnsupportedOperationException("MockSpringBeanFactory does not support explicit bean creation arguments)");
        }
        Object bean = this.singletonMap.get(name);
        if (bean != null) {
            return bean;
        }
        Class<?> type = this.prototypeMap.get(name);
        if (type != null) {
            try {
                return type.newInstance();
            } catch (RuntimeException e) {
                throw e;
            } catch (Exception e) {
                throw new BeanInstantiationException(type, e.getMessage(), e);
            }
        }
        throw new NoSuchBeanDefinitionException(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public boolean containsBean(String name) {
        return this.everybodyMap.containsKey(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        boolean r = this.singletonMap.containsKey(name);
        if (!r && !this.prototypeMap.containsKey(name)) {
            throw new NoSuchBeanDefinitionException(name);
        }
        return r;
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public boolean isPrototype(String name) throws NoSuchBeanDefinitionException {
        boolean r = this.prototypeMap.containsKey(name);
        if (!r && !this.singletonMap.containsKey(name)) {
            throw new NoSuchBeanDefinitionException(name);
        }
        return r;
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException {
        Class type = this.getType(name);
        return (targetType == null || (type != null && targetType.isAssignableFrom(type)));
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public Class getType(String name) throws NoSuchBeanDefinitionException {
        Class type = this.everybodyMap.get(name);
        if (type != null) {
            return type;
        }
        throw new NoSuchBeanDefinitionException(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public String[] getAliases(String name) {
        return EMPTY_STRING_ARRAY;
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public boolean containsBeanDefinition(String beanName) {
        return this.everybodyMap.containsKey(beanName);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public int getBeanDefinitionCount() {
        return this.everybodyMap.size();
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public String[] getBeanDefinitionNames() {
        if (this.everybodyMap.isEmpty()) {
            return EMPTY_STRING_ARRAY;
        }
        String[] result = new String[this.everybodyMap.size()];
        return this.everybodyMap.keySet().toArray(result);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public String[] getBeanNamesForType(Class type) {
        return this.getBeanNamesForType(type, true, true);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) {
        Set<String> names = this.namesForTypes.get(type);
        if (names == null) {
            return EMPTY_STRING_ARRAY;
        }
        if (!includeNonSingletons) {
            names = new HashSet<String>(names);
            names.removeAll(this.prototypeMap.keySet());
        }
        String[] result = new String[names.size()];
        return names.toArray(result);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public Map getBeansOfType(Class type) throws BeansException {
        return this.getBeansOfType(type, true, true);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    public Map<String, Object> getBeansOfType(Class type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException {
        String[] beanNames = this.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
        Map<String, Object> result = new LinkedHashMap<String, Object>(beanNames.length);
        for (int i = 0; i < beanNames.length; i++) {
            String beanName = beanNames[i];
            result.put(beanName, this.getBean(beanName));
        }
        return result;
    }

}
